unit QueryConditions;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils;

const
  CON_OR = ' or ';
  CON_AND = ' and ';

type

  { TCondicaoConsulta }

  /// Classe para criar condições para consultas

  { TQueryConditions }

  TQueryConditions = class
  private
    /// Lista de condições
    ConditionList: TStringList;
    /// Indica se as condições devem ser concatenadas com OR
    Conectivo: string;
    /// Prepara uma string para ser usada com o operador SIMILAR
    procedure PrepareString(var Value: string);
  public
    /// Construtor
    constructor Create(ConectivoPadrao: string);
    /// Destrutor
    destructor Destroy();
    /// Adiciona uma condição do tipo "Field = Value"
    /// @param(Field é o nome do campo)
    /// @param(Value é o valor para o campo)
    procedure Add(Field: string; Value: string);
    /// Adiciona uma condição do tipo junção com "Field = Field2"
    /// @param(Field1 é o nome do campo 1)
    /// @param(Field2 é o nome do campo 2)
    procedure AddJoinField(Field1: string; Field2: string);
    /// Adiciona uma condição do tipo "(Field1 = Value1 LogicOperator Field2 = Value2)"
    /// @param(Field1 é o nome do campo 1)
    /// @param(Value1 é o valor para o campo 1)
    /// @param(Field2 é o nome do campo 2)
    /// @param(Value2 é o valor para o campo 2)
    /// @param(LogicOperator é um operador lógico como AND ou OR)
    procedure AddFieldOpField(Field1: string; Value1: string; Field2: string; Value2: string; LogicOperator: string);
    /// Adiciona uma condição do tipo "Field LIKE %Value%". Deve ser usado para campos textuais
    /// @param(Field é o nome do campo)
    /// @param(Value é o valor para o campo)
    procedure AddText(Field: string; Value: string);
    /// Obtém as condições concatenadas para serem usadas no WHERE de uma consulta
    /// @return(String com as condições para serem usaras no WHERE de uma consulta)
    function GetWhereConditions(): string;
    /// Obtém o número de condições
    /// @return(Número de consições que o objeto possui)
    function GetCount(): integer;
  end;


implementation

uses LazUTF8;

{ TConcicaoConsulta }

procedure TQueryConditions.PrepareString(var Value: string);
var
  I, L: integer;
  LetraAtual: string;
begin
  // Passa a string para maiúsculo
  Value := UTF8UpperCase(Value);
  // Obtém o tamanho da string
  L := UTF8Length(Value);
  // Percorre todos os caracteres da string
  for I := L downto 1 do begin
    // Obtém a letra atual
    LetraAtual := UTF8Copy(Value, I, 1);
    // Troca cada caractere encontrado por uma expressão regular para considerar todos os acentos
    if UTF8Pos(LetraAtual, 'aàáââäÀÁÃÂÄ') > 0 then begin
      UTF8Delete(Value, I, 1);
      UTF8Insert('(a|à|á|â|â|ä|À|Á|Ã|Â|Ä)', Value, I);
    end else
    if UTF8Pos(LetraAtual, 'eèéẽêëÈÉẼÊË') > 0 then begin
      UTF8Delete(Value, I, 1);
      UTF8Insert('(e|è|é|ẽ|ê|ë|E|È|É|Ẽ|Ê|Ë)', Value, I);
    end else
    if UTF8Pos(LetraAtual, 'iìíĩîïÌÍĨÎÏ') > 0 then begin
      UTF8Delete(Value, I, 1);
      UTF8Insert('(i|ì|í|ĩ|î|ï|Ì|Í|Ĩ|Î|Ï)', Value, I);
    end else
    if UTF8Pos(LetraAtual, 'oòóõôöÒÓÕÔÖ') > 0 then begin
      UTF8Delete(Value, I, 1);
      UTF8Insert('(o|ò|ó|õ|ô|ö|O|Ò|Ó|Õ|Ô|Ö)', Value, I);
    end else
    if UTF8Pos(LetraAtual, 'uùúũûüÙÚŨÛÜ') > 0 then begin
      UTF8Delete(Value, I, 1);
      UTF8Insert('(u|ù|ú|ũ|û|ü|Ù|Ú|Ũ|Û|Ü)', Value, I);
    end else
    if UTF8Pos(LetraAtual, 'CÇc') > 0 then begin
      UTF8Delete(Value, I, 1);
      UTF8Insert('(C|Ç|ç)', Value, I);
    end;
  end;
end;

constructor TQueryConditions.Create(ConectivoPadrao: string);
begin
  if (ConectivoPadrao = CON_OR) then begin
    self.Conectivo := CON_OR;
  end else begin
    self.Conectivo := CON_AND;
  end;
  // Inicializa a lista de condições
  ConditionList := TStringList.Create();
end;

destructor TQueryConditions.Destroy();
begin
  // Destroy a lista de condições
  ConditionList.Destroy();
end;

procedure TQueryConditions.Add(Field: string; Value: string);
begin
  // Testa se o campo e o valor não são vazios
  if (Trim(Field) <> '') and (Trim(Value) <> '') then begin
    Value := QuotedStr(Value);
    ConditionList.Add(Field + ' = ' + Value);
  end;
end;

procedure TQueryConditions.AddJoinField(Field1: string; Field2: string);
begin
  // Testa se os dois campos não são vazios
  if (Trim(Field1) <> '') and (Trim(Field2) <> '') then begin
    ConditionList.Add(Field1 + ' = ' + Field2);
  end;
end;

procedure TQueryConditions.AddFieldOpField(Field1: string; Value1: string; Field2: string;
  Value2: string; LogicOperator: string);
begin
  // Testa se os campos e valores não são vazios
  if (Trim(Field1) <> '') and (Trim(Value1) <> '') and (Trim(Field2) <> '') and (Trim(Value2) <> '') then begin
    Value1 := QuotedStr(Value1);
    Value2 := QuotedStr(Value2);
    ConditionList.Add('(' + Field1 + ' = ' + Value1 + LogicOperator + Field2 + ' = ' + Value2 + ')');
  end;
end;

procedure TQueryConditions.AddText(Field: string; Value: string);
begin
  // Testa se o campo e o valor não são vazios
  if (Trim(Field) <> '') and (Trim(Value) <> '') then begin
    PrepareString(Value);
    Value := '%' + Value + '%';
    Value := QuotedStr(Value);
    Field := 'UPPER(' + Field + ')';
    Value := 'UPPER(' + Value + ')';
    ConditionList.Add(Field + ' SIMILAR TO ' + Value);
  end;
end;

function TQueryConditions.GetWhereConditions(): string;
var
  Condicoes: string;
  Cont: integer;
begin
  Condicoes := '';
  // Verifica se há ao menos uma condição
  if (ConditionList.Count >= 1) then begin
    // Inicia com espaço para não juntar com WHERE
    Condicoes := Condicoes + ' ';
    // Concatena a primeira condição
    Condicoes := Condicoes + ConditionList[0];
    // Concatena as demais condições
    for Cont := 1 to ConditionList.Count - 1 do begin
      Condicoes := Condicoes + Conectivo + ConditionList[Cont];
    end;
  end;
  GetWhereConditions := Condicoes;
end;

function TQueryConditions.GetCount(): integer;
begin
  GetCount := ConditionList.Count;
end;

end.
